// 客户端处理模块：负责服务器端处理单个客户端连接，
//包括用户认证、消息处理和客户端退出逻辑。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "client_handler.h"
#include "user_db.h"
#include "log.h"
#include "broadcast.h"


/**
 *  处理单个客户端连接的主函数，运行在独立线程中。
 * 
 * 该函数负责处理客户端的注册、登录认证，消息接收与处理，
 * 以及客户端断开连接时的清理工作。
 * 
 *  arg 指向客户端套接字文件描述符的指针，需要通过解引用获取到具体的文件描述符值。
 * return void* 该函数无实际返回值，按约定返回 NULL。
 */
void *handle_client(void *arg) {
    // 将传入的参数转换为 int 指针并解引用，获取客户端套接字文件描述符
    int client_socket = *(int *)arg;
    free(arg); //释放动态分配的内存，避免内存泄漏

    char buffer[BUFFER_SIZE];   //初始化接收客户端消息的缓冲区
    char username[50];  //初始化存储客户端用户名
    int client_idx = -1;    //初始化存储客户端在数组中的索引，-1表示未找到
    int authenticated = 0;  //客户端认证状态，0 表示未认证，1 表示已认证

    // 认证循环，直到客户端认证成功或断开连接
    while (!authenticated) {
        // 清空缓冲区，确保每次接收消息前缓冲区为空
        memset(buffer, 0, BUFFER_SIZE);
        // 从客户端套接字接收消息，返回接收到的字节数
        //sockfd 客户端套接字文件描述符
        //buf 指向接收数据的缓冲区的指针
        //count 要接收的最大字节数
        //flags 接收选项，通常为 0
        int bytes_recv = recv(client_socket, buffer, BUFFER_SIZE, 0);//成功--返回实际接收的字节数，失败-1
        // 若接收到的字节数小于等于 0，说明客户端断开连接
        if (bytes_recv <= 0) {
            // 关闭客户端套接字
            close(client_socket);
            // 退出当前线程
            pthread_exit(NULL);
        }
        
        // 处理注册请求
        //比较buffer前10个字符是否为"/register "
        //若相对应，说明客户端发送了注册请求----进入注册处理逻辑
        if (strncmp(buffer, "/register ", 10) == 0) {
            // 用于存储注册时的用户名和密码
            char reg_username[50], reg_password[50];
            // 从注册请求中解析用户名和密码，检查是否成功解析出两个参数
            if (sscanf(buffer + 10, "%49s %49s", reg_username, reg_password) == 2) {
                // 检查用户名是否包含非法字符
                if (!is_valid_username(reg_username)) {
                    // 若用户名非法，向客户端发送错误消息
                    char msg[] = "[系统] 注册失败：用户名包含非法字符(*#/&)\n";
                    // 发送注册失败消息给客户端
                    send(client_socket, msg, strlen(msg), 0);
                    continue;
                }
                
                // 用于处理客户端的用户注册请求。
                // 检查用户名是否合法，若合法则调用 register_user 函数进行注册操作
                // 若注册成功，返回 1；若用户名已存在，返回 0；若数据库错误，返回 -1。
                //使用register_user函数注册用户，并尝试将新用户信息存入数据库，
                //并返回注册结果存储在reg_redult中
                int reg_result = register_user(reg_username, reg_password);
                if (reg_result == 1) {
                    // 注册成功，向客户端发送成功消息
                    char msg[BUFFER_SIZE];
                    //将格式化字符串和实际用户名组合为完整信息存放到msg数组中
                    snprintf(msg, sizeof(msg), "[系统] 注册成功！用户名: %s\n", reg_username);
                    send(client_socket, msg, strlen(msg), 0);//将注册成功消息发送给客户端
                    // 记录注册日志
                    log_message("REGISTER", reg_username, "新用户注册", NULL);
                } else if (reg_result == 0) {
                    // 用户名已存在，向客户端发送失败消息
                    char msg[BUFFER_SIZE];
                    snprintf(msg, sizeof(msg), "[系统] 注册失败：该用户已存在\n");
                    send(client_socket, msg, strlen(msg), 0);
                } else {
                    // 数据库错误，向客户端发送失败消息
                    char msg[] = "[系统] 注册失败：数据库错误\n";
                    send(client_socket, msg, strlen(msg), 0);
                }
            } else {
                // 注册命令格式错误，向客户端发送错误消息
                char msg[] = "[系统] 注册命令格式错误\n";
                send(client_socket, msg, strlen(msg), 0);
            }
        }
        // 检查是否为登录请求
        else if (strncmp(buffer, "/login ", 7) == 0) {
            // 用于存储登录时的用户名和密码
            char login_username[50], login_password[50];
            // 从消息中解析用户名和密码，检查是否成功解析出两个参数
            if (sscanf(buffer + 7, "%49s %49s", login_username, login_password) == 2) {

               //调用 login_user 函数对客户端的登录请求进行验证，
               //将验证结果存储在 login_result 变量里  
               //返回值：
               //1--登录成功
               //0--登录失败（用户名或密码错误）
               //-1--登录失败（用户不存在）
                int login_result = login_user(login_username, login_password);
                if (login_result == 1) {
                    // 登录成功，向客户端发送成功消息
                    char msg[BUFFER_SIZE];
                    snprintf(msg, sizeof(msg), "[系统] 登录成功！欢迎 %s\n", login_username);
                    send(client_socket, msg, strlen(msg), 0);
                    // 将登录用户名复制到 username 数组
                    strcpy(username, login_username);
                    // 标记客户端已认证
                    authenticated = 1;
                    // 记录登录日志
                    log_message("LOGIN", login_username, "用户登录", NULL);
                } else {
                    // 登录失败，向客户端发送失败消息
                    char msg[] = "[系统] 登录失败：用户名或密码错误，请检查或注册\n";
                    send(client_socket, msg, strlen(msg), 0);
                }
            } else {
                // 登录命令格式错误，向客户端发送错误消息
                char msg[] = "[系统] 登录命令格式错误\n";
                send(client_socket, msg, strlen(msg), 0);
            }
        }
        // 处理退出命令
        //将存储客户端发送消息的缓冲区和字符串/exit进行对比，返回值：
        //0--不相等
        //1--相等
        else if (strcmp(buffer, "/exit") == 0) {
            // 关闭客户端套接字
            close(client_socket);
            // 退出当前线程
            pthread_exit(NULL);
        }
        else {
            // 未知命令，向客户端发送提示消息
            char msg[] = "[系统] 未知命令，请使用/register或/login或/exit\n";
            send(client_socket, msg, strlen(msg), 0);
        }
    }
    
    // 记录用户连接日志
    log_message("CONNECT", username, "用户加入聊天室", NULL);
    // 打印客户端连接信息
    printf("客户端【%s】已连接到服务器\n", username);

    // 分配客户端槽位
    // 加锁，确保在修改客户端列表时数据的一致性
    pthread_mutex_lock(&mutex);
    //从0开始遍历client数组，直到数组末尾
    for (int i = 0; i < MAX_CLIENTS; i++) {
        //检查当前索引i对应的clients数组元素的socket字段是否等于-1
        //若等于-1，说明该槽位当前未被占用
        //找到空闲槽位，将客户端信息存入
        if (clients[i].socket == -1) {
            clients[i].socket = client_socket;  //将客户端套接字client_socket赋值给clients[i].socket
            strcpy(clients[i].username, username);  //将用户名username复制到clients[i].username字段中
            clients[i].in_private_mode = 0;     //将客户端当前是否在私聊模式的状态设置为0，即非私聊模式
            clients[i].is_authenticated = 1;    //将客户端当前是否已认证的状态设置为1，即已认证
            client_idx = i; //将当前客户端在clients数组中的索引i赋值给client_idx
            break;
        }
    }
    // 解锁，允许其他线程访问客户端列表
    pthread_mutex_unlock(&mutex);

    // 若未找到空闲槽位，说明聊天室已满
    if (client_idx == -1) {
        // 向客户端发送聊天室已满的消息
        char msg[] = "聊天室已满，无法加入\n";
        send(client_socket, msg, strlen(msg), 0);
        // 关闭客户端套接字
        close(client_socket);
        // 退出当前线程
        pthread_exit(NULL);
    }

    // 新用户加入时广播欢迎消息
    char join_msg[BUFFER_SIZE];
    sprintf(join_msg, "欢迎【%s】加入聊天室\n", username);

    //调用broadcast_message函数，将join_msg字符串中的内容发送给所有已认证的客户端
    //NULL：表示不发送给任何特定的客户端，即广播给所有已认证的客户端
    //clients：指向Client结构体数组的指针，用于存储所有已认证的客户端信息
    //join_msg：指向包含欢迎消息的字符串的指针
    //-1：表示发送给所有已认证的客户端
    //0：表示群聊
    //clients：指向Client结构体数组的指针，用于存储所有已认证的客户端信息
    broadcast_message(NULL, join_msg, -1, 0, clients);

    // 广播在线用户列表
    char online_users[BUFFER_SIZE]; //用于存储在线用户列表的字符串
    //调用get_online_users函数，将当前在线用户的列表字符串存储到online_users字符数组中
    get_online_users(online_users, clients);    
    //将online_users字符数组中的内容发送给当前客户端，用于显示在线用户列表
    send(client_socket, online_users, strlen(online_users), 0);
    //调用broadcast_message函数，将online_users字符串中的内容发送给所有已认证的客户端
    broadcast_message(NULL, online_users, -1, 0, clients);

    // 消息循环，持续接收客户端消息
    while (1) {
        // 清空缓冲区，确保每次接收消息前缓冲区为空
        memset(buffer, 0, BUFFER_SIZE);
        // 从客户端套接字接收消息，返回接收到的字节数
        int bytes_recv = recv(client_socket, buffer, BUFFER_SIZE, 0);
        // 若接收到的字节数小于等于 0，说明客户端异常断开连接
        if (bytes_recv <= 0) {
            // 打印客户端异常断开连接信息
            printf("客户端【%s】异常断开连接\n", username);
            break;
        }

        // 退出私聊逻辑
        if (strcmp(buffer, "/exit") == 0 && clients[client_idx].in_private_mode) {
            // 存储私聊目标用户名
            char target[50];
            strcpy(target, clients[client_idx].private_target);
            
            // 遍历客户端列表，找到私聊目标客户端
            for (int i = 0; i < MAX_CLIENTS; i++) {
                if (clients[i].socket != -1 && strcmp(clients[i].username, target) == 0 && clients[i].in_private_mode) {
                    // 向私聊目标客户端发送私聊结束消息
                    char msg[BUFFER_SIZE];
                    sprintf(msg, "[系统] %s 结束了与你的私聊\n", username);
                    send(clients[i].socket, msg, strlen(msg), 0);
                    // 关闭私聊目标客户端的私聊模式
                    clients[i].in_private_mode = 0;
                    break;
                }
            }
            
            // 关闭当前客户端的私聊模式
            clients[client_idx].in_private_mode = 0;
            // 向当前客户端发送私聊结束消息
            char msg[BUFFER_SIZE];
            sprintf(msg, "[系统] 你与 %s 的私聊已结束\n", target);
            send(client_socket, msg, strlen(msg), 0);
            continue;
        }

        // 退出聊天室命令
        if (strcmp(buffer, "/exit") == 0) {
            break;
        }

        // 私聊逻辑
        if (buffer[0] == '@') {
            // 存储私聊目标用户名和消息内容
            char target[50], msg[BUFFER_SIZE];
            sscanf(buffer, "@%49s %[^\n]", target, msg);
            
            // 标记目标用户是否存在
            int target_exist = 0;
            // 加锁，确保在查找目标用户时数据的一致性
            pthread_mutex_lock(&mutex);
            // 遍历客户端列表，查找目标用户
            for (int i = 0; i < MAX_CLIENTS; i++) {
                if (clients[i].socket != -1 && clients[i].is_authenticated && 
                    strcmp(clients[i].username, target) == 0) {
                    // 找到目标用户，标记为存在
                    target_exist = 1;
                    break;
                }
            }
            // 解锁，允许其他线程访问客户端列表
            pthread_mutex_unlock(&mutex);
            
            if (target_exist) {
                // 若目标用户存在，发送私聊消息
                send_private_message(username, target, msg, clients);
                // 记录私聊消息日志
                log_message("PRIVATE_MSG", username, msg, target);
            }
            else {
                // 若目标用户不存在或不在线，向客户端发送错误消息
                char err_msg[BUFFER_SIZE];
                sprintf(err_msg, "[系统] 用户【%s】不存在或不在线\n", target);
                send(client_socket, err_msg, strlen(err_msg), 0);
            }
        } 
        else {
            // 普通群聊，广播消息给所有在线客户端
            broadcast_message(username, buffer, client_socket, 0, clients);
            
            // 记录群聊消息日志
            log_message("GROUP_MSG", username, buffer, NULL);
        }
    }

    // 客户端退出逻辑
    // 加锁，确保在清理客户端信息时数据的一致性
    pthread_mutex_lock(&mutex);
    if (client_idx != -1 && clients[client_idx].socket != -1) {
        // 记录用户退出日志
        log_message("DISCONNECT", username, "用户离开聊天室", NULL);
        
        // 广播用户退出消息
        char leave_msg[BUFFER_SIZE];
        sprintf(leave_msg, "用户【%s】退出聊天室\n", username);
        broadcast_message(NULL, leave_msg, -1, 0, clients);
        
        // 清理私聊状态
        if (clients[client_idx].in_private_mode) {
            // 存储私聊目标用户名
            char target[50];
            strcpy(target, clients[client_idx].private_target);
            // 遍历客户端列表，找到私聊目标客户端
            for (int i = 0; i < MAX_CLIENTS; i++) {
                if (clients[i].socket != -1 && strcmp(clients[i].username, target) == 0 && clients[i].in_private_mode) {
                    // 向私聊目标客户端发送用户离开消息
                    char msg[BUFFER_SIZE];
                    sprintf(msg, "[系统] %s 已离开聊天室，私聊已结束\n", username);
                    send(clients[i].socket, msg, strlen(msg), 0);
                    // 关闭私聊目标客户端的私聊模式
                    clients[i].in_private_mode = 0;
                    break;
                }
            }
        }

        // 清理客户端槽位
        clients[client_idx].socket = -1;
        clients[client_idx].is_authenticated = 0;
        // 关闭客户端套接字
        close(client_socket);

        // 更新在线用户列表并广播
        char online_users[BUFFER_SIZE];
        get_online_users(online_users, clients);
        broadcast_message(NULL, online_users, -1, 0, clients);
    }
    // 解锁，允许其他线程访问客户端列表
    pthread_mutex_unlock(&mutex);

    // 退出当前线程
    pthread_exit(NULL);
}